#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <inttypes.h>
#include <iostream>
#include <ctime>
#include "map_reduce.h"
#include "stddefines.h"
#include "histogram.h"

WORD red_keys[256];
WORD green_keys[256];
WORD blue_keys[256];

using namespace std;

int main(int argc, char *argv[]) {
    
    final_data_t hist_vals;
    int i;
    int fd;
    char *pdata;
    //struct stat finfo;
    char * filename;
    BITMAPFILEHEADER BmpFileHdr;
    BITMAPINFOHEADER BmpInfoHdr;
    clock_t start_time, end_time;


    //Record Start time
    start_time = clock();

    // Load Command Line
    if (argc != 2)
    {
        printf("[USAGE]: %s <BMP filename>\n", argv[0]);
        exit(1);
    }
    
    filename = argv[1];

    cout << "[INFO] Loading BMP File Header from "<< filename <<"!" << endl;
    CHECK_ERROR(load_bitmap_header(filename, &BmpFileHdr, &BmpInfoHdr)==false);
    

    // Read in the file
    CHECK_ERROR((fd = open(filename, O_RDONLY)) < 0);
    // Get the file info (for file length)
    //CHECK_ERROR(fstat(fd, &finfo) < 0);

    // Memory map the file

    cout << "[INFO] Loading Image Data from "<< filename <<"!" << endl;
    CHECK_ERROR((pdata = (char*)mmap(0, BmpFileHdr.bfSize + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) == NULL);

    int img_byte_size = BmpFileHdr.bfSize - BmpFileHdr.bfOffBits;

    cout << "[INFO] Total Image Byte Size is " << img_byte_size << endl;
 
    //Initialize Keys
    for (i = 0; i < 256; i++) {
        blue_keys[i] = i;
        green_keys[i] = 1000 + i;
        red_keys[i] = 2000 + i;
    }

    CHECK_ERROR (map_reduce_init ());

    // Configure map reduce parameters
    cout << "[INFO] Configuring Map Reduce Parameters... " << endl;

    map_reduce_args_t map_reduce_args;
    memset(&map_reduce_args, 0, sizeof(map_reduce_args_t));

    map_reduce_args.task_data = &pdata[BmpFileHdr.bfOffBits];    //&hist_data;
    map_reduce_args.data_size = img_byte_size;
    map_reduce_args.result = &hist_vals;


    map_reduce_args.map = hist_mapper;
    map_reduce_args.reduce = hist_reducer;
    map_reduce_args.combiner = hist_merger;
    map_reduce_args.splitter = NULL; 
    map_reduce_args.key_cmp = valcmp;
    
    map_reduce_args.unit_size = 3;  // 3 bytes per pixel
    map_reduce_args.partition = NULL; 

    map_reduce_args.L1_cache_size = 32*1024;
    map_reduce_args.num_map_threads = 2;
    map_reduce_args.num_reduce_threads = 2; 
    map_reduce_args.num_merge_threads = 2;
    map_reduce_args.num_procs = 2;
    map_reduce_args.key_match_factor = 2;

    cout << "[INFO] Starting Map Reduce For Histogram ... " << endl;

    CHECK_ERROR( map_reduce (&map_reduce_args) < 0);

    // Final Stage
    CHECK_ERROR (map_reduce_finalize ());

    //Record end time
    end_time = clock();
    printf("[INFO] MapReduce Completion Time is %.2f seconds\n", (double)(end_time-start_time)/CLOCKS_PER_SEC);

    // Print Histogram
    WORD pix_val;
    intptr_t freq;
    WORD prev = 0;
    int pixel_num = img_byte_size/3;
    cout <<"[INFO] Printing Histogram Results:" << endl;
    cout <<"\nBlue\n";
    cout <<"----------\n\n";
    for (i = 0; i < hist_vals.length; i++)
    {
        keyval_t * curr = &((keyval_t *)hist_vals.data)[i];
        pix_val = *((short *)curr->key);
        freq = (intptr_t)curr->val;
        
        if (pix_val - prev > 700) {
            if (pix_val >= 2000) {
                cout << "\n\nRed\n";
                cout << "----------\n\n";
            }
            else if (pix_val >= 1000) {
                cout << "\n\nGreen\n";
                cout << "----------\n\n";
            }
        }
        
	int stars = (double)freq / (pixel_num*1.0) * 512.0;

	printf("%3d(%7d) ", pix_val % 1000, freq);
	for(int j=0;j<stars&&j<128; j++) {
	  cout << "X";
	}

	cout << endl;

        prev = pix_val;
    }


    CHECK_ERROR (munmap (pdata, BmpFileHdr.bfSize + 1) < 0);
    free(hist_vals.data);
    close (fd);

    return 0;
}
